package wortubung.quiz;

import java.io.BufferedReader;
import java.io.File;
import java.io.LineNumberReader;
import java.util.*;

import wortubung.WortUbung;
import wortubung.core.*;
import wortubung.util.PrintableDelegate;

import static wortubung.util.CommonUtils.*;

public class QuizManager extends PrintableDelegate {
    public static final String ANSWER_MARK = "::::";

	private List<MEANING> nounMeaningList = new ArrayList<MEANING>();
	private List<MEANING> verbMeaningList = new ArrayList<MEANING>();
	private List<MEANING> otherMeaningList = new ArrayList<MEANING>();

	public List<MEANING> getDefinitionsByPOS(POSCategory pos) {
		switch(pos) {
		case Noun: return nounMeaningList;
		case Verb: return verbMeaningList;
		default:   return otherMeaningList;
		}
	}

	public QuizManager(WortUbung dictionary) {
		super(dictionary);

		for (WORD wd : dictionary.getCurrentLanguageManager().getWords()) {
			List<MEANING> meanings = wd.getDefinition();
			if (meanings != null) {
    			for (MEANING mng : meanings) {
        			switch(mng.getPOSCategory()) {
        			case Noun: nounMeaningList.add(mng);
        			case Verb: verbMeaningList.add(mng);
        			default:   otherMeaningList.add(mng);
        			}
    			}
			}
		}
	}

	public WortUbung getDictionary() { return (WortUbung)getPrintable(); }

	public MultiChoiceQuestion[] generateMultiChoiceQuiz(Collection<WORD> testWords, int numChoices) {
		return generateMultiChoiceQuiz(randomize(testWords), numChoices);
	}

	public MultiChoiceQuestion[] generateMultiChoiceQuiz(WORD[] testWords, int numChoices) {
		MultiChoiceQuestion[] ret = new MultiChoiceQuestion[testWords.length];
		for (int i=testWords.length-1; i>=0; --i) {
			WORD wd = testWords[i];
			MultiChoiceQuestion mcq = new MultiChoiceQuestion(wd.getWord(), numChoices, wd.getDefaultPOS(), wd.getLang(), i);
			MEANING def = wd.getDefinitionForTest();
			boolean noneOfAbove = (def == null) || random.nextDouble() < 0.2;
			int j = numChoices;
			if (!noneOfAbove)
				--j;
			if (!noneOfAbove)
				mcq.addAnswer(def.getMeaning());
			List<MEANING> meanings = getDefinitionsByPOS(def != null ? def.getPOSCategory() : wd.getPOSCategory());
			for (; j>0; --j) {
				do {
					def = meanings.get(random.nextInt(meanings.size()));
				} while (def.getHost() == wd);
				if (!mcq.addChoice(def.getMeaning()))
					++j; // redo
			}
			if (noneOfAbove)
				mcq.addNoneOfAboveChoice();
			ret[i] = mcq;
		}
		return ret;
	}

	public void doOrPrintQuiz(QuizGenOptions options, BufferedReader input) {
		boolean isPrint = (input == null);
		WORD[] words = options.query(getDictionary().getCurrentLanguageManager(), false);
		if (words != null) {
			randomize(words);
			getDictionary().setPrinting(true);
	    	try {
	    		if (isPrint) {
    	    		setPrinting(true);
    	        	openOutputFile(options.getOutputFileName());
	    		}
				if (options.isMultiChoice())
	        		doOrPrintMultiChoiceQuiz(options, words, input);
	        	else if (options.isCrossMatch())
	        		doOrPrintCrossMatchQuiz(options, words, input);
	    	} catch(Exception e) {
	    		error(e);
	    	} finally {
	    		setPrinting(false);
	    		try {
	    			if (isPrint)
	    				closeOutputFile();
	    		} catch(Exception e) {
	        		error(e);
	    		}
	    	}
		}
	}

	private void doOrPrintMultiChoiceQuiz(QuizGenOptions options, WORD[] words, BufferedReader input) {
		boolean isPrint = (input == null);
		MultiChoiceQuestion[] qs = generateMultiChoiceQuiz(words, options.getSuiteSize());
    	int numDigits = numDigits(qs.length);
    	String fmt = "%" + numDigits + "d. %s%s";
    	String prefix = makeLine(' ', numDigits + 2);
    	String answerPrefix = prefix + "    - Your answer: ";
    	int textlen = options.getPageWidth() - 4 - numDigits - 2;
    	String separator = prefix + makeLine('.', textlen + 4);
    	int idx = 1;
    	ArrayList<String> lines = new ArrayList<String>();
    	ArrayList<MultiChoiceQuestion> correctOnes = new ArrayList<MultiChoiceQuestion>();
    	ArrayList<MultiChoiceQuestion> failedOnes = new ArrayList<MultiChoiceQuestion>();
    	for (MultiChoiceQuestion q : qs) {
    		printfln(fmt, idx++, q.getWord(), q.getSuffix());
    		char choice = 'A';
    		for (String c : q.getChoices()) {
    			lines.clear();
    			wrapText(c, textlen, lines);
    			for (int i=0; i<lines.size(); ++i) {
    				if (i == 0)
    					printfln("%s(%s) %s", prefix, choice++, lines.get(0));
    				else
    					println(prefix + "    " + lines.get(i));
    			}
    		}
    		println(separator);
    		if (isPrint) {
        		String answerText = encryptAnswer(q.getAnswer()) + ANSWER_MARK;
        		println(makeStringLen(answerPrefix + "[ ]", 0, separator.length()-answerText.length(), ' ') + answerText);
    		} else { // interactive
    			promptln(rightFlush(options.getPageWidth(), "(Answer is case-insensitive. To abort the quiz, just type '!', without quotes.)"));
        		promptln(separator);
        		prompt(answerPrefix);
        		String resp = null;
        		while (resp == null) {
            		try { resp = input.readLine().trim(); } catch(Exception e) {}
        		}
        		if ("!".equals(resp))
        			break; // abort
    			boolean correct = (resp.length() > 0) && (q.getAnswer() == letterToInt(resp.charAt(0)));
    			(correct ? correctOnes : failedOnes).add(q);
    			if (options.isInstantAnswer())
        			promptln(rightFlush(options.getPageWidth(), correct ? "{ Correct }" : ("{ Wrong: should be " + intToLetter(q.getAnswer()) + " }")));
    		}
    		println();
    	}
    	
    	if (!isPrint) { // interactive
    		promptln(prefix + "Result (correct/total): " + correctOnes.size() + '/' + qs.length + '\n');
    		if (correctOnes.isEmpty() || failedOnes.isEmpty())
    			return;
    	    promptln(concatEvenly("Correct words:", true, getDictionary().getPageWidth(), -1, null, correctOnes.toArray(new String[correctOnes.size()])));
    	    promptln(concatEvenly(" Failed words:", true, getDictionary().getPageWidth(), -1, null, failedOnes.toArray(new String[failedOnes.size()])));
    	}
    }

    private void doOrPrintCrossMatchQuiz(QuizGenOptions options, WORD[] words, BufferedReader input) {
		int i, j;
		int rightCount = 0;
		int totalTests = 0;
		int suiteSize = options.getSuiteSize();
		int numSuites = words.length / suiteSize;
		CrossMatchingSuite suite = new CrossMatchingSuite();
		for (i=0; i<numSuites; ++i) {
			for (j=0; j<suiteSize; ++j)
				suite.add(words[i*suiteSize + j]);
			rightCount += doOrPrintCrossMatchingSuite(suite, i+1, numSuites+1, options, input);
			totalTests += suite.size();
			if (options.exceedsTotal(totalTests))
				break;
			suite.reset();
		}
		j = words.length - numSuites * suiteSize;
		if (!options.exceedsTotal(totalTests) && (j > 2)) {
			for (; j>0; --j)
				suite.add(words[words.length - j]);
			if (suite.size() > 2) {
    			rightCount += doOrPrintCrossMatchingSuite(suite, i+1, numSuites+1, options, input);
    			totalTests += suite.size();
			}
		}
		
		if (input != null) { // interactive
    		promptln("\n       Result (correct/total): " + rightCount + '/' + totalTests + '\n');
		}
    }

	private int doOrPrintCrossMatchingSuite(CrossMatchingSuite suite, int index, int totalSuites, QuizGenOptions options, BufferedReader input) {
		boolean isPrint = (input == null);
		int rightCount = 0;

		int i, numDigits = numDigits(totalSuites);
		println();
		printfln("%" + numDigits + "d. MATCH EACH WORD IN THE 2ND PART WITH ONE OF THE DESCRIPTIONS IN THE 1ST:", index);
		String prefix = makeLine(' ', numDigits + 2);
		int textlen = options.getPageWidth() - 4 - numDigits - 2;
		String separator = prefix + makeLine('.', textlen + 4);
		char choice = 'A';
    	ArrayList<String> lines = new ArrayList<String>();
    	int widthLeft = 10;
		for (i=0; i<suite.size(); ++i) {
			lines.clear();
			wrapText(suite.getRight(i), textlen, lines);
			for (int j=0; j<lines.size(); ++j) {
				if (j == 0)
					printfln("%s(%s) %s", prefix, choice++, lines.get(0));
				else
					println(prefix + "    " + lines.get(j));
			}
			int wl = suite.getLeft(i).length();
			if (wl > widthLeft)
				widthLeft = wl;
		}
		println(separator);
		String fmt;
		if (isPrint) {
    		fmt = "%s    %" + numDigits(suite.size()) + "d. %-" + (widthLeft+4) + "s - Your answer: [ ]";
    		for (i=0; i<suite.size(); ++i) {
        		String answer = encryptAnswer(suite.getAnswer(i));
        		String wordPart = String.format(fmt, prefix, i+1, suite.getLeft(i));
        		println(makeStringLen(wordPart, 0, separator.length()-answer.length()-ANSWER_MARK.length(), ' ') + answer + ANSWER_MARK);
    		}
    		println(separator);
		} else { // interactive
			numDigits = numDigits(suite.size());
			fmt = "%" + numDigits + "d. %-" + suite.getMaxLeftLength() + "s  ";
			int cols = (options.getPageWidth() - prefix.length()) / (numDigits + suite.getMaxLeftLength() + 4);
    		for (i=0; i<suite.size(); ++i) {
    			if (i == 0)
    				prompt(prefix);
    			else if (i % cols == 0)
    				prompt("\n" + prefix);
        		promptf(fmt, i+1, suite.getLeft(i));
    		}
			promptln("\n" + separator);
			promptln(rightFlush(options.getPageWidth(), "(Answer like this: 1=c 2=d ....)"));
    		promptln(separator);
        	prompt(prefix + "    - Your answers: ");
    		String resp = null;
    		while (resp == null) {
        		try { resp = input.readLine().trim(); } catch(Exception e) {}
    		}
    		if ("!".equals(resp))
    			return 0; // abort
    		for (String r : resp.split(" +")) {
    			String[] lr = r.split("=");
    			try {
    				int ans = suite.getAnswer(Integer.parseInt(lr[0]) - 1);
    				boolean correct = ans == letterToInt(lr[1].charAt(0));
    				if (correct)
    					++rightCount;
    				promptfln("%s    %-6s%s", prefix, r, correct ? "{ Correct } " : "{ Wrong: should be " + intToLetter(ans) + " }");
    			} catch(Exception e) {}
    		}
		}
		println();
		return rightCount;
	}

	public void scoreQuizes(String params) {
		String[] fileNames = params == null ? null : params.split(" +");
		if (length(fileNames) == 0) {
			promptln("/scorequiz must specify one or more quiz files.");
			return;
		}

		String line;
    	for (String fn : fileNames) {
    		try {
    	    	int rightCount = 0;
    	    	int wrongCount = 0;
    			LineNumberReader lnr = openFile(new File(fn));
    			while ((line = lnr.readLine()) != null) {
    				Boolean result = checkAnswer(line);
    				if (result != null) {
    					if (result)
    						++rightCount;
    					else
    						++wrongCount;
    				}
    			}
    			lnr.close();
    			if (rightCount + wrongCount == 0)
    				continue;
    			promptfln("Quiz file '%s' result (correct/total): %d/%d", fn, rightCount, rightCount+wrongCount);
    		} catch(Exception e) {
    			error(e);
    		}
    		promptln();
    	}
	}

	public static Boolean checkAnswer(String line) {
		if (line.endsWith(ANSWER_MARK)) {
			int idx = line.lastIndexOf(']');
			if (idx < 0)
				return null;
			int answer = decryptAnswer(line.substring(idx+1, line.length() - ANSWER_MARK.length()).trim());
			int idx1 = line.lastIndexOf('[');
			if (idx1 < 0 || idx1 > idx)
				return null;
			String respTxt = line.substring(idx1+1, idx).trim();
			return (respTxt.length() > 0) && (answer == letterToInt(respTxt.charAt(0)));
		}
		return null;
	}

	public static int letterToInt(char c) {
		return (int)(Character.toLowerCase(c) - 'a');
	}
	
	public static char intToLetter(int i) {
		return (char)('A' + i);
	}
}
